package org.eoti.ai.genetic.gp;

import java.util.ArrayList;
import java.util.List;

/**
 * The MIT License
 * <p/>
 * Copyright (c) 2009 Malachi de AElfweald (malachid@gmail.com)
 * <p/>
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
 * documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to the following conditions:
 * <p/>
 * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
 * Software.
 * <p/>
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
 * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
public abstract class Simulation<DATA,FITNESS>
{
	private ArrayList<FITNESS> history;
	private Generation<DATA,FITNESS> lastGeneration;
	private Generation<DATA, FITNESS> currentGeneration;
	private Criteria<DATA,FITNESS> criteria;
	private int generation = 0;

	public Simulation()
	{
		history = new ArrayList<FITNESS>();
	}

	@SuppressWarnings("unchecked")
	public Program<DATA,FITNESS> simulate()
	{
		currentGeneration = createGeneration(generation);
		currentGeneration.randomize();

		List<Program<DATA,FITNESS>> bestOfBreed = currentGeneration.bestOfBreed();
		Program<DATA,FITNESS> best = bestOfBreed.get(0);
		FITNESS bestFitness = criteria.getFitness().getFitness( best );

		int numRepeats = 0;
		FITNESS repeatFitness = bestFitness;

		while(!shouldTerminate(bestFitness))
		{
			save();

			// always start with Reproduction
			boolean started = false;

			while(currentGeneration.size() < lastGeneration.size())
			{
//				displayCurrentGeneration();

				Evolution<DATA,FITNESS> evolution = (started ? criteria.getRandomEvolution() : new Reproduction());
				started = true;
				for(Program<DATA,FITNESS> program : evolution.evolve( bestOfBreed ))
					currentGeneration.add(program);
			}
			bestOfBreed = currentGeneration.bestOfBreed();
			best = bestOfBreed.get(0);
			bestFitness = criteria.getFitness().getFitness(best);

			if(repeatFitness.equals(bestFitness))
			{
				numRepeats++;
				if(criteria.getMaxRepeats() >= 0 && criteria.getMaxRepeats() < numRepeats)
					return best;
			}else{
				numRepeats = 0;
				repeatFitness = bestFitness;
			}
		}

		return best;
	}

	public List<FITNESS> getHistory(){return history;}

	protected void save()
	{
		displayCurrentGeneration();
		history.add(criteria.getFitness().getFitness(currentGeneration.bestOfBreed().get( 0 )));
		lastGeneration = currentGeneration;
		currentGeneration = createGeneration(++generation);
	}

	protected void displayCurrentGeneration()
	{
		if(!criteria.isDebug()) return;
		
		System.out.format("-----------------------------------------------------------------\n");
		System.out.format("Gen: %s SIZE=%s\n", currentGeneration.getId(), currentGeneration.size());
		for(Program<DATA,FITNESS> program : currentGeneration.bestOfBreed())
			System.out.format("\t%s = %s\n", program, criteria.getFitness().getFitness(program));
	}

	public abstract Generation<DATA,FITNESS> createGeneration(int generationNum);

	protected boolean shouldTerminate(FITNESS bestFitness)
	{
		if(generation >= criteria.getMaxGenerations())
			return true;

		if(criteria.getFitness().getFitnessComparator().compare( bestFitness, criteria.getFitnessThreshold() ) <= 0)
			return true;

		return false;
	}

	public Generation<DATA, FITNESS> getCurrentGeneration(){
		return currentGeneration;
	}

	public void setCurrentGeneration( Generation<DATA, FITNESS> currentGeneration ){
		this.currentGeneration = currentGeneration;
	}

	public Criteria<DATA, FITNESS> getCriteria(){
		return criteria;
	}

	public void setCriteria( Criteria<DATA, FITNESS> criteria ){
		this.criteria = criteria;
	}
}
